﻿using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Windows.Media;
using System.IO;
using System.Collections.Generic;
using StreamingClient.StreamManagment;
using StreamingClient.TSSplitter;

namespace StreamingClient.Player
{
    public class VlcTsMediaStreamSource : MediaStreamSource
    {
        #region Fields and Properties

        private const int PacketLength = 188;
        private const int MinBufferLimit = 300;

        private readonly ManualResetEvent _dataAvailable = new ManualResetEvent(false);
        private readonly ManualResetEvent _audioAvailable = new ManualResetEvent(false);
        private readonly ManualResetEvent _videoAvailable = new ManualResetEvent(false);

        readonly Queue<TimestampedSample> _audioSamples = new Queue<TimestampedSample>(20480);
        readonly Queue<TimestampedSample> _videoSamples = new Queue<TimestampedSample>(10480);
        private TimeSpan? _timeStampOffset;

        private readonly Thread _loader;
        private readonly Stream _mediaStream;
        private MemoryStream _audioStream;
        private MemoryStream _videoStream;

        private MediaStreamDescription _audioStreamDescription;
        private MediaStreamDescription _videoStreamDescription;
        private byte[] _codecPrivateData;
        private readonly Dictionary<MediaSampleAttributeKeys, string> _emptyDict = new Dictionary<MediaSampleAttributeKeys, string>();
        private MpegLayer3WaveFormat _mpegLayer3WaveFormat;

        private readonly StreamingSettings _settings;
        private readonly Dictionary<int, long> _statistic = new Dictionary<int, long>();

        private int AudioPID
        {
            get { return VideoPID - 1; }
        }

        private int VideoPID { get; set; }

        public delegate void LogHandler(string message);

        public event LogHandler LogEvent;

        #endregion

        #region Constructor

        public VlcTsMediaStreamSource(StreamingSettings  settings,Stream stream)
        {
            _settings = settings;
            _mediaStream = stream;

            _loader= new Thread(DecodeLive);
            _loader.Name = "Loader Thread";
            _loader.Start();
            _dataAvailable.WaitOne();
        }

        #endregion

        #region TS decoder

        private bool TryReadTSPacket(out TSPacket packet)
        {
            packet = ReadTSPacket();

            return packet != null;
        }

        private TSPacket ReadTSPacket()
        {
            var buffer = new byte[PacketLength];
            var cnt = _mediaStream.Read(buffer, 0, PacketLength);
            int bytesRead;
            while (cnt<PacketLength)
            {
                Thread.Sleep(100);
                try
                {
                    bytesRead = _mediaStream.Read(buffer, cnt, PacketLength - cnt);
                }
                catch (Exception)
                {
                    return null;
                }

                if (_mediaStream.CanSeek && _mediaStream.Position == _mediaStream.Length)
                {
                    return null;
                }
                
                
                cnt += bytesRead;
            }
            
            return new TSPacket(buffer);
        }

        private void DecodeLive()
        {
            TSPacket packet;
            
            while (TryReadTSPacket(out packet))
            {
                MemoryStream ms = null;
                if (!_statistic.ContainsKey(packet.PID))
                {
                    Log("0x" + packet.PID.ToString("X"));
                    _statistic.Add(packet.PID,0);
                }

                _statistic[packet.PID]++;

                if (packet.HasPayloadStart)
                {
                    if (VideoPID == 0 && packet.IsVideo)
                    {
                        _codecPrivateData = packet.GetCodecPrivateData();
                        if (_codecPrivateData != null)
                        {
                            VideoPID = packet.PID;
                            _dataAvailable.Set();
                        }
                    }


                    if (packet.PID == AudioPID)
                    {
                        if (_audioStream != null)
                        {
                            if (_timeStampOffset == null)
                            {
                                _timeStampOffset = packet.PES.PTS;
                            }
                            _audioSamples.Enqueue( new TimestampedSample(packet.PES.PTS,_audioStream));
                            if (_audioSamples.Count > MinBufferLimit)
                            {
                                _audioAvailable.Set();
                            }
                        }
                        ms = _audioStream = new MemoryStream();
                    }
                    else if (packet.PID == VideoPID)
                    {
                        if (_videoStream != null)
                        {
                            TimeSpan timeStamp=TimeSpan.Zero;
                            if (packet.PES!=null)
                            {
                                timeStamp = packet.PES.PTS;
                            }
                            _videoSamples.Enqueue(new TimestampedSample(timeStamp, _videoStream));
                            if (_videoSamples.Count > MinBufferLimit)
                            {
                                _videoAvailable.Set();
                            }
                        }
                        ms = _videoStream = new MemoryStream();
                    }
                }
                else
                {
                    if (packet.PID == AudioPID)
                    {
                        ms = _audioStream;
                    }
                    else if (packet.PID == VideoPID)
                    {
                        ms = _videoStream;
                    }
                }
                if (ms != null)
                {
                    if (packet.PES != null)
                    {
                        ms.Write(packet._packet, packet.PES.ESSENCE_OFFSET, PacketLength - packet.PES.ESSENCE_OFFSET);
                    }
                    else
                    {
                        ms.Write(packet._packet, packet.ESSENCE_OFFSET, PacketLength - packet.ESSENCE_OFFSET);
                    }
                }
            }
            _mediaStream.Dispose();

            Log("Media stream ended");
        }    

        #endregion

        #region Frame provider

        private MediaStreamSample GetSampleVideo()
        {
            return GetSample(_videoSamples,_videoAvailable,"video",_videoStreamDescription);
        }

        private MediaStreamSample GetSample(Queue<TimestampedSample> queue, ManualResetEvent availableEvent, string type, MediaStreamDescription streamDescription)
        {
            while (queue.Count == 0)
            {
                Log(type + " empty");
                ReportGetSampleProgress(0);
 
                if (_mediaStream.CanSeek)
                {
                    return null;
                }

                availableEvent.WaitOne();
                availableEvent.Reset();
            }
            long ticks=-1;
            Stream stream=null;
            while (ticks < 0)
            {
                var sample = queue.Dequeue();
                stream = sample.Stream;

                ticks = sample.Timestamp.Ticks - _timeStampOffset.Value.Ticks;
            }


            MediaStreamSample mediaStreamSample = new MediaStreamSample(streamDescription, stream, 0, stream.Length,ticks, _emptyDict);
            return mediaStreamSample;
        }

        private MediaStreamSample GetSampleAudio()
        {
            return GetSample(_audioSamples, _audioAvailable, "audio", _audioStreamDescription);
        }

        #endregion
        
        #region Diagnostics

        public string GetStatistics()
        {
            var sb = new StringBuilder();
            sb.AppendLine("Video-PID: " + VideoPID);
            sb.AppendLine("Audio-PID: " + AudioPID);
            sb.AppendLine("Video-Buffer: " + _videoSamples.Count);
            sb.AppendLine("Audio-Buffer: " + _audioSamples.Count);
            sb.AppendLine("Samples per PID: " );
            foreach (var stat in _statistic)
            {
                sb.AppendLine("  " + stat.Key + ": " + stat.Value);
            }

            return sb.ToString();
        }

        protected void Log(string message)
        {
            LogHandler handler = LogEvent;
            if (handler != null)
            {
                handler(message);
            }
            else
            {
                Debug.WriteLine(message);
            }
        }

        #endregion
        
        #region Overrides of MediaStreamSource

        protected override void OpenMediaAsync()
        {
            _dataAvailable.WaitOne();


            Dictionary<MediaStreamAttributeKeys, string> audioStreamAttributes = new Dictionary<MediaStreamAttributeKeys, string>();

            //Audio
            var wfx = new WaveFormatExtensible();
            this._mpegLayer3WaveFormat = new MpegLayer3WaveFormat();
            this._mpegLayer3WaveFormat.WaveFormatExtensible = wfx;

            this._mpegLayer3WaveFormat.WaveFormatExtensible.FormatTag = 0x55;
            this._mpegLayer3WaveFormat.WaveFormatExtensible.Channels = (short)_settings.AChannels;
            this._mpegLayer3WaveFormat.WaveFormatExtensible.SamplesPerSec = _settings.ASampleRate;
            this._mpegLayer3WaveFormat.WaveFormatExtensible.AverageBytesPerSecond = _settings.ABitrate*1000/8;
            this._mpegLayer3WaveFormat.WaveFormatExtensible.BlockAlign = 1;
            this._mpegLayer3WaveFormat.WaveFormatExtensible.BitsPerSample = 0;
            this._mpegLayer3WaveFormat.WaveFormatExtensible.Size = 12;

            this._mpegLayer3WaveFormat.Id = 1;
            this._mpegLayer3WaveFormat.BitratePaddingMode = 0;
            this._mpegLayer3WaveFormat.FramesPerBlock = 1;
            this._mpegLayer3WaveFormat.BlockSize = (short)(144 * _settings.ABitrate * 1000 / _settings.ASampleRate);
            this._mpegLayer3WaveFormat.CodecDelay = 1393;

            audioStreamAttributes[MediaStreamAttributeKeys.CodecPrivateData] = this._mpegLayer3WaveFormat.ToHexString();


            this._audioStreamDescription = new MediaStreamDescription(MediaStreamType.Audio, audioStreamAttributes);

            //Video
            Dictionary<MediaStreamAttributeKeys, string> videoStreamAttributes = new Dictionary<MediaStreamAttributeKeys, string>();
            videoStreamAttributes[MediaStreamAttributeKeys.VideoFourCC] = "H264";
            string cpc = "";
            foreach (byte b in _codecPrivateData)
            {
                cpc += string.Format("{0:X2}", b);
            }
            videoStreamAttributes[MediaStreamAttributeKeys.CodecPrivateData] = cpc;
            videoStreamAttributes[MediaStreamAttributeKeys.Height] = _settings.VHeight.ToString();
            videoStreamAttributes[MediaStreamAttributeKeys.Width] = _settings.VWidth.ToString();
            this._videoStreamDescription = new MediaStreamDescription(MediaStreamType.Video, videoStreamAttributes);

            //Media
            Dictionary<MediaSourceAttributesKeys, string> mediaSourceAttributes = new Dictionary<MediaSourceAttributesKeys, string>();
            mediaSourceAttributes[MediaSourceAttributesKeys.CanSeek] = "0";

            List<MediaStreamDescription> mediaStreamDescriptions = new List<MediaStreamDescription>();
            mediaStreamDescriptions.Add(this._audioStreamDescription);
            mediaStreamDescriptions.Add(this._videoStreamDescription);

            this.ReportOpenMediaCompleted(mediaSourceAttributes, mediaStreamDescriptions);
        }

        protected override void GetSampleAsync(MediaStreamType mediaStreamType)
        {

            MediaStreamSample mediaStreamSample = null;

            if (mediaStreamType == MediaStreamType.Audio)
                mediaStreamSample = this.GetSampleAudio();
            else if (mediaStreamType == MediaStreamType.Video)
                mediaStreamSample = this.GetSampleVideo();
            if (mediaStreamSample != null)
            {
                this.ReportGetSampleCompleted(mediaStreamSample);
            }
            else
            {
                new MediaStreamSample(null, null, 0, 0, 0, _emptyDict);
            }
        }

        protected override void SeekAsync(long seekToTime)
        {
            this.ReportSeekCompleted(seekToTime);
        }

        protected override void CloseMedia()
        {
            _mediaStream.Close();
        }

        protected override void SwitchMediaStreamAsync(MediaStreamDescription mediaStreamDescription)
        {
        }

        protected override void GetDiagnosticAsync(MediaStreamSourceDiagnosticKind diagnosticKind)
        {
        }

        #endregion
    }
}
